package com.hugang.security;

/**
 * 线程安全问题示例：多个窗口卖票，使用实现 Runnable接口方式
 * 1、问题：卖票过程中，出现了重票、错票 -->出现线程安全问题
 * 2、问题出现原因：当某个线程操作车票的过程中，尚未完成操作时，其他线程参与进来，也操作车票
 * 3、如何解决：当一个线程a操作ticket时，其他线程不能参与进来，直到线程a操作完ticket时，
 * 其他线程才可以开始操作ticket。即使线程a出现了阻塞，也不能改变
 * 4、java中，通过同步机制解决线程安全问题
 * <p>
 * 方式一：同步代码块
 * <p>
 * synchronized(同步监视器){
 * //需要同步的代码
 * }
 * 说明：
 * 1、需要同步的代码：操作共享数据的代码 --> 不能包含多的代码，也不能包含少的代码
 * 2、共享数据：多个线程共同操作的变量，比如本例的ticket
 * 3、同步监视器：俗称锁，任何一个类的对象，都可以充当锁
 * 要求：多个线程使用同一个锁
 * 补充：在实现Runnable接口创建多线程的方式中，可以考虑使用this充当同步监视器
 * <p>
 * <p>
 * 方式二：同步方法
 * 如果操作共享数据的代码完整地声明在一个方法中，可以将此方法声明为同步的。
 * 总结：
 *      1、同步方法仍然涉及到同步监视器，只是不需要显示地声明
 *      2、非静态的同步方法，同步监视器为this；静态的同步方法，同步监视器为此类的Class对象
 * <p>
 * <p>
 * <p>
 * 5、同步的方式，解决了线程安全的问题。 ---好处
 * 操作同步代码时，只能有一个线程参与，其他线程等待，相当于是一个单线程的过程，效率低。 ---局限性
 *
 * @author hg
 * @date 2020/6/15 09:52
 */
public class WindowRunnableTest {

    public static void main(String[] args) {
//        WindowRunnable window = new WindowRunnable();
        WindowRunnable2 window = new WindowRunnable2();
        Thread thread1 = new Thread(window);
        Thread thread2 = new Thread(window);
        Thread thread3 = new Thread(window);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();

    }

}

class WindowRunnable implements Runnable {

    private int ticket = 100;
//    Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            //使用同步代码块方式
//            synchronized (obj) {
            //this表示唯一的window对象
            synchronized (this) {
                if (ticket > 0) {

                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票，票号为：" + ticket--);
                } else {
                    break;
                }
            }
        }
    }
}

class WindowRunnable2 implements Runnable {

    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            show();
        }
    }

    /**
     * 使用同步方法方式：同步监视器为this
     */
    public synchronized void show() {
        if (ticket > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":卖票，票号为：" + ticket--);
        }
    }
}